#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>


#include "conn.h"
#include "net.h"
#include "request.h"

#include "../threads/thread.h"

#include <glog/logging.h>

conn_t* connection_new()
{
    conn_t* conn = NULL;

    conn =(conn_t*)calloc(1, sizeof(conn_t));

    conn->evbuf = evbuffer_new();
    if (conn->evbuf == NULL) {
        free(conn);
        return NULL;
    }

    return conn;
}

void connection_free(conn_t* conn)
{
    if (conn == NULL){
        return;
	}

    if (conn->evbuf != NULL) {
        evbuffer_free(conn->evbuf);
    }

    if (conn->fd > 0) {
        int ret = close(conn->fd);
		int try_times = 0;
		while (0 != ret) {
			if (errno != EINTR || errno == EBADF){
				break;
			}
			ret = close(conn->fd);
			++try_times;
			if(5 == try_times){
				LOG(WARNING) << "colse conn->fd failed!, try_times=5, NOTICE:a fd will lost";
				break;
			}
		}
    }

    free(conn);
    conn = NULL;
}

//
extern request_t* request_new();
extern void request_free(request_t* request);
#include "../uginx.h"
//

void pack_request(void* msg, size_t len, void*data)
{
    conn_t* conn = (conn_t*)data;
	request_t* request = request_new();
	if (request == NULL) {
		connection_free(conn);
		return;
	}
	if(len > 4096){//is necessary #define *** 4096 ?
		LOG(WARNING) << "command length >4096 and will be discard; connectioin=" << inet_ntoa(conn->cliaddr.sin_addr) << ":" << ntohs(conn->cliaddr.sin_port) << " sockfd=" << conn->fd << " command=" << msg;

		connection_free(conn);
		request_free(request);
		return;
	}

	request->buffer_size = len;
	request->sockfd = conn->fd;
	memcpy((void*)request->buffer, msg, len);

	int index = conn->index % settings.num_threads_per_process ;

	if (thread_dispatch_one(index, request) == -1) {
		connection_free(conn);
		request_free(request);
	}

	return;
}


void connection_input(struct bufferevent* bufev, void* data)
{
    conn_t* conn = (conn_t*)data;
    struct evbuffer *evbuf = conn->evbuf;

    evbuffer_add_buffer(evbuf, bufev->input);

    while (1) {
        size_t len = 0;
        char* command = evbuffer_readln(evbuf, &len, EVBUFFER_EOL_CRLF);
        if (!command) {
            return;
        }

		LOG(WARNING) << "connectioin=" << inet_ntoa(conn->cliaddr.sin_addr) << ":" << ntohs(conn->cliaddr.sin_port) << " sockfd=" << conn->fd << " command=" << command;
		pack_request(command, len, data);
        free(command);
    }
}

void connection_output(struct bufferevent* bufev, void* data)
{
	printf("enter connection_output\n");
}

void connection_error(struct bufferevent* bufev, short what, void* data)
{
}

void connection_accept(int sockfd, short event, void* data)
{
    struct event_base* evbase = (struct event_base*)data;
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    memset(&cliaddr, 0, sizeof(cliaddr));
    int connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &len);
    if (connfd < 0) {    
        LOG(WARNING) << "accept failed! (" << strerror(errno) << ")";
        return;
    }
    if (setnonblock(connfd) == -1) {    
        LOG(WARNING) << "setnonblock failed!";
        close(connfd);
        return;
    }

    conn_t* conn = connection_new();
    if (conn == NULL) {
        LOG(WARNING) << "connection_new failed!";
        close(connfd);
        return;
    }

    conn->fd = connfd;
    conn->index = connfd + 3;//skip possibility 0,1,2 (that is stdin,stdout,stderr)
    conn->cliaddr = cliaddr;
    conn->bufev = bufferevent_new(connfd, 
                                  connection_input, 
                                  connection_output, 
                                  connection_error, 
                                  conn);
    if (conn->bufev == NULL) {
        LOG(WARNING) << "bufferevent_new (" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << ") failed";
        connection_free(conn);
        return;
    }

    bufferevent_settimeout(conn->bufev, 100, 0);
    bufferevent_base_set(evbase, conn->bufev);
    bufferevent_enable(conn->bufev, EV_READ);
}
